﻿using System;
using System.Windows.Interop;
using System.Runtime.InteropServices;

// MORE INFO ABOUT CLIPBOARD HOOKS - http://dedjo.blogspot.com/2008/01/clipboard-and-wpf-as-hooks-and.html
namespace ObjectShelve.Clipboard
{
	/// <summary>
	/// Class to perform various task with Windows Clipboard, like hook change event, etc.
	/// </summary>
	public sealed class ClipboardHook : IClipboardHook, IDisposable
	{
		private EventHandler<ClipboardHookEventArgs> _clipboardChangedEvent;

		private ClipboardHook()
		{ }

		static ClipboardHook()
		{ }

		private static readonly ClipboardHook _instance = new ClipboardHook();

		public static IClipboardHook GetClipboard()
		{
			return _instance;
		}

		#region IClipboard Members

		event EventHandler<ClipboardHookEventArgs> IClipboardHook.ClipboardChanged
		{
			add
			{
				_clipboardChangedEvent += value;
			}
			remove
			{
				_clipboardChangedEvent -= value;
			}
		}

		bool IClipboardHook.Hook(HwndSource target)
		{
			if (null != target)
			{
				_installedHandle = target.Handle;
				_viewerHandle = SetClipboardViewer(_installedHandle);
				target.AddHook(new HwndSourceHook(hwndSourceHook));
				return true;
			}
			return false;
		}

		bool IClipboardHook.Unhook()
		{
			ChangeClipboardChain(_installedHandle, _viewerHandle);
			int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
			return (error == 0);
		}

		#endregion

		IntPtr hwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
		{
			switch (msg)
			{
				case WM_CHANGECBCHAIN:
					_viewerHandle = lParam;
					if (_viewerHandle != IntPtr.Zero)
					{
						SendMessage(_viewerHandle, msg, wParam, lParam);
					}

					break;

				case WM_DRAWCLIPBOARD:
					if (null != _clipboardChangedEvent)
					{
						_clipboardChangedEvent.Invoke(this, new ClipboardHookEventArgs(System.Windows.Clipboard.GetDataObject()));
					}

					if (_viewerHandle != IntPtr.Zero)
					{
						SendMessage(_viewerHandle, msg, wParam, lParam);
					}

					break;

			}
			return IntPtr.Zero;
		}

		#region Interop
		IntPtr _viewerHandle;
		IntPtr _installedHandle;

		const int WM_DRAWCLIPBOARD = 0x308;
		const int WM_CHANGECBCHAIN = 0x30D;

		[DllImport("user32.dll")]
		private extern static IntPtr SetClipboardViewer(IntPtr hWnd);

		[DllImport("user32.dll")]
		private extern static int ChangeClipboardChain(IntPtr hWnd, IntPtr hWndNext);

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		private extern static int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (_installedHandle != IntPtr.Zero)
			{
				ChangeClipboardChain(_installedHandle, _viewerHandle);
			}
		}


		#endregion
	}
}
